﻿//+------------------------------------------------------------------------------------------------------------------------------------------------------
//
//Description: Packaging helper.
//Author: Aravind Ramarathinam (aravr@microsoft.com)
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.IO.Packaging;
using System.Windows.Forms;
using VmroleAuthor.ViewModels;

namespace VmroleAuthor.Helpers
{

    public enum CloudServiceManagerFileType
    {
        ResourceExtensionPackage,
        ResourceDefinitionPackage,
        ResourceDefinition,
        ResourceConfiguration
    }

    public class PackagingFormatHelper
    {
        private const int BufferSize = 0x4000;
        public const string ResExtPkg = ".resextpkg";
        public const string ResDefPkg = ".resdefpkg";
        public const string ResExt = ".resext";
        public const string ResDef = ".resdef";
        public const string ViewDef = ".viewdef";
        public const string Resources = ".resources";

        public static CloudServiceManagerFileType GetFileType(string filePath)
        {          
            if (filePath.EndsWith(ResExtPkg, StringComparison.OrdinalIgnoreCase))
            {
                return CloudServiceManagerFileType.ResourceExtensionPackage;
            }
            if (filePath.EndsWith(ResDefPkg, StringComparison.OrdinalIgnoreCase))
            {
                return CloudServiceManagerFileType.ResourceDefinitionPackage;
            }
            if (filePath.EndsWith(ResDef, StringComparison.OrdinalIgnoreCase))
            {
                return CloudServiceManagerFileType.ResourceDefinition;
            }
            return CloudServiceManagerFileType.ResourceConfiguration;
        }

        public static bool IsPackage(string filePath)
        {
            return filePath.EndsWith(ResExtPkg, StringComparison.OrdinalIgnoreCase) || filePath.EndsWith(ResDefPkg, StringComparison.OrdinalIgnoreCase);
        }

        public static void GetResextPkgDetails(string extractedPkgFolder, out string jsonFileName)
        {                     
            IEnumerable<string> files = Directory.EnumerateFiles(extractedPkgFolder);
            jsonFileName = files.FirstOrDefault(file => file.EndsWith(ResExt, StringComparison.OrdinalIgnoreCase));
            if (string.IsNullOrEmpty(jsonFileName))
            {
                throw new ApplicationException(string.Format("No .reset json file found in the extracted package dir {0} with the extension {1}", extractedPkgFolder, ResExt));
            }          
        }

        public static void GetResdefPkgDetails(
            string extractedPkgFolder,
            out string resdefJsonFile,
            out string viewdefJsonFile,
            out string smallIconFile,
            out string mediumIconFile,
            out string largeIconFile,
            out List<string> resourceFileNames)
        {
            IEnumerable<string> files = Directory.EnumerateFiles(extractedPkgFolder);
            IEnumerable<string> enumerable = files as IList<string> ?? files.ToList();
            resdefJsonFile = enumerable.FirstOrDefault(file => file.EndsWith( ResDef, StringComparison.OrdinalIgnoreCase));
            if (string.IsNullOrEmpty(resdefJsonFile))
            {
                throw new ApplicationException(string.Format("No resource definition json file found in the extracted package dir {0} with the extension {1}", extractedPkgFolder, ResDef));
            }
            viewdefJsonFile = enumerable.FirstOrDefault(file => file.EndsWith(ViewDef, StringComparison.OrdinalIgnoreCase));
            smallIconFile = enumerable.FirstOrDefault(file => file.EndsWith(ViewDefViewModel.SmallIconFileName, StringComparison.OrdinalIgnoreCase));
            mediumIconFile = enumerable.FirstOrDefault(file => file.EndsWith(ViewDefViewModel.MediumIconFileName, StringComparison.OrdinalIgnoreCase));
            largeIconFile = enumerable.FirstOrDefault(file => file.EndsWith(ViewDefViewModel.LargeIconFileName, StringComparison.OrdinalIgnoreCase));

            // Enumerate Resource Files
            resourceFileNames = new List<string>();
            IEnumerable<string> dirs = Directory.EnumerateDirectories(extractedPkgFolder);
            foreach (string dir in dirs)
            {
                IEnumerable<string> resourceFiles = Directory.EnumerateFiles(dir);
                string resourceFile = resourceFiles.FirstOrDefault(file => file.EndsWith(Resources, StringComparison.OrdinalIgnoreCase));
                if (!string.IsNullOrEmpty(resourceFile))
                {
                    resourceFileNames.Add(resourceFile);
                }
            }
        }

        #region ExtractPackage
        public static void ExtractPackage(string inputPackagePath, string outputFolder, CloudServiceManagerFileType csmFileType)
        {
            if (string.IsNullOrEmpty(inputPackagePath) || !File.Exists(inputPackagePath))
            {
                throw new ApplicationException("Please specify a valid package File, Its absent");
            }

            if (csmFileType == CloudServiceManagerFileType.ResourceExtensionPackage)
            {
                if (!string.Equals(Path.GetExtension(inputPackagePath), PackagingFormatHelper.ResExtPkg, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ApplicationException(string.Format("Please specify a valid package File with {0} extension", PackagingFormatHelper.ResExtPkg));
                }
            }
            else if (csmFileType == CloudServiceManagerFileType.ResourceDefinitionPackage)
            {
                if (!string.Equals(Path.GetExtension(inputPackagePath), PackagingFormatHelper.ResDefPkg, StringComparison.OrdinalIgnoreCase))
                {
                    throw new ApplicationException(string.Format("Please specify a valid package File with {0} extension", PackagingFormatHelper.ResDefPkg));
                }
            }                       
            if (Directory.Exists(outputFolder))
            {
                //Console.WriteLine("A folder {0} already exists, Cleaning it up", outFolder);
                Directory.Delete(outputFolder, true);
            }
            PackagingFormatHelper.ExtractFiles(inputPackagePath, outputFolder);
        }
        static private void ExtractFiles(string inputPackagePath, string outputFolder)
        {
            using (Package package = Package.Open(inputPackagePath, FileMode.Open, FileAccess.Read))
            {
                PackagePartCollection packageParts = package.GetParts();
                foreach (PackagePart packagePart in packageParts)
                {
                    PackagingFormatHelper.ExtractSingleFile(packagePart, outputFolder);
                }
            }
        }
        static private void ExtractSingleFile(PackagePart packagePart, string outputFolder)
        {
            string unescapedFilePath = Uri.UnescapeDataString(packagePart.Uri.OriginalString);
            Stream inputStream = packagePart.GetStream(FileMode.Open, FileAccess.Read);

            unescapedFilePath = unescapedFilePath.TrimStart(new[] { Path.AltDirectorySeparatorChar });
            string destinationFileName = Path.Combine(outputFolder, unescapedFilePath);
            string fileDir = Path.GetDirectoryName(destinationFileName);
            if (string.IsNullOrEmpty(fileDir))
            {
                throw new ApplicationException("Malformed package");
            }
            if (!Directory.Exists(fileDir))
            {
                Directory.CreateDirectory(fileDir);
            }
            using (FileStream outputStream = new FileStream(destinationFileName, FileMode.CreateNew))
            {
                PackagingFormatHelper.CopyStream(inputStream, outputStream);
            }
        }
        #endregion ExtractPackage

        #region CreatePackage
        static public void CreatePackage(string inputFolderPath, string outputPackageFilePath)
        {
            if (!Directory.Exists(inputFolderPath))
            {
                MessageBox.Show("Please specify a valid package directory");
                return;
            }
            using (Package package = Package.Open(outputPackageFilePath, FileMode.Create))
            {
                CreatePackageForDirectory(".", inputFolderPath, package, true);
            }
        }
        static private void CreatePackageForDirectory(string relativePath, string inputFolderPath, Package package, bool isRootDirectory = false)
        {
            foreach (string file in Directory.GetFiles(inputFolderPath))
            {
                string fileName = Path.GetFileName(file);
                Debug.Assert(fileName != null);
                Uri uri = PackUriHelper.CreatePartUri(isRootDirectory ?
                            new Uri(fileName, UriKind.Relative) :
                            new Uri(Path.Combine(relativePath, fileName), UriKind.Relative));

                // Create compressed packages
                PackagePart partDocument = package.CreatePart(uri, MediaTypeNames.Application.Octet, CompressionOption.Normal);
                if (partDocument == null)
                {
                    throw new ApplicationException(string.Format("File {0} in {1} is malformed", file, inputFolderPath));
                }
                using (FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    CopyStream(stream, partDocument.GetStream());
                }
            }
            // Recursively go and add the remaining folders
            foreach (string child in Directory.GetDirectories(inputFolderPath))
            {
                string fileName = Path.GetFileName(child);
                Debug.Assert(fileName != null);
                CreatePackageForDirectory(Path.Combine(relativePath, fileName), child, package);
            }
        }     
        #endregion CreatePackage

        private static void CopyStream(Stream input, Stream output)
        {
            var buf = new byte[PackagingFormatHelper.BufferSize];
            int bytesRead;
            while ((bytesRead = input.Read(buf, 0, PackagingFormatHelper.BufferSize)) > 0)
            {
                output.Write(buf, 0, bytesRead);
            }
        }

    }
}
